/**
 * @file 对象池化装饰器
 * @author DuskyHuang 圣子
*/

import { Component, instantiate, Prefab, Node } from "cc";
import { IPool, IPoolable } from "../tools";
import { swapper } from "./swapper";

declare interface IPoolConstructor extends IPool {
    new(...args: any[]): IPoolable;
    __pool?: IPoolable[];
}

export const poolify: DecoratorSwapper<ClassDecorator, [number?]>
    = swapper<ClassDecorator>(([clazz]: [IPoolConstructor], size?: number) => {
        function create(ctor: any): IPoolable {
            if (ctor.prototype instanceof Component) {
                const node = ctor.template instanceof Prefab ? instantiate(ctor.template) : new Node;
                return (node.getComponent(ctor) ?? node.addComponent(ctor)) as IPoolable;
            } else return new ctor();
        }
        Object.defineProperties(clazz, {
            __pool: {
                configurable: true, enumerable: false, writable: false,
                value: [],
            },
            prefab: {
                configurable: true, enumerable: false, writable: true,
                value: null,
            },
            size: {
                configurable: true, enumerable: false,
                get: function (this: IPoolConstructor & { __pool: IPoolable[]; }) {
                    return this.__pool?.length ?? 0;
                }
            },
            claim: {
                configurable: true, enumerable: false, writable: false,
                value: function (this: IPoolConstructor & { __pool: IPoolable[]; container?: Node }, ...initParams: any[]) {
                    // use pop() may much faster than shift() in large-size array
                    let item = this.__pool.pop() ?? create(clazz);
                    if (item instanceof Component && this.container instanceof Node) {
                        item.node.parent = this.container;
                    }
                    Function.invoke(item.claimed, item, ...initParams);
                    return item;
                }
            },
            free: {
                configurable: true, enumerable: false, writable: false,
                value: function (this: IPoolConstructor, item: IPoolable) {
                    if (item instanceof Component && !!item.node?.parent) {
                        item.node.removeFromParent();
                    }
                    Function.invoke(item.freed, item);
                    this.__pool.push(item);
                }
            },
            clear: {
                configurable: true, enumerable: false, writable: false,
                value: function (this: IPoolConstructor) {
                    this.__pool.forEach(item => {
                        if (item instanceof Component) {
                            (item.node ?? item).destroy();
                        }
                    });
                    this.__pool.length = 0;
                }
            },
            dump: {
                configurable: true, enumerable: false, writable: false,
                value: function (this: IPoolConstructor) {
                    console.info(this.name, this.__pool.length, this.__pool);
                }
            }
        });
        Object.defineProperty(clazz.prototype, 'free', {
            configurable: true, enumerable: false, writable: false,
            value: function (this: IPoolable) {
                (clazz as IPoolConstructor).free(this);
            }
        });
        if (isNum(size)) {
            if (clazz instanceof Component) console.warn('Init size with Component-class may created empty-node')
            if (size > 1000) return console.warn('Init size too larger');
            for (let i = 0; i < size; i++) {
                clazz.__pool.push(create(clazz));
            }
        }
    });